home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / DB.PY < prev    next >
Encoding:
Python Source  |  2000-09-16  |  21.7 KB  |  611 lines

  1. ##############################################################################
  2. # Zope Public License (ZPL) Version 1.0
  3. # -------------------------------------
  4. # Copyright (c) Digital Creations.  All rights reserved.
  5. # This license has been certified as Open Source(tm).
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions are
  8. # met:
  9. # 1. Redistributions in source code must retain the above copyright
  10. #    notice, this list of conditions, and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. #    notice, this list of conditions, and the following disclaimer in
  13. #    the documentation and/or other materials provided with the
  14. #    distribution.
  15. # 3. Digital Creations requests that attribution be given to Zope
  16. #    in any manner possible. Zope includes a "Powered by Zope"
  17. #    button that is installed by default. While it is not a license
  18. #    violation to remove this button, it is requested that the
  19. #    attribution remain. A significant investment has been put
  20. #    into Zope, and this effort will continue if the Zope community
  21. #    continues to grow. This is one way to assure that growth.
  22. # 4. All advertising materials and documentation mentioning
  23. #    features derived from or use of this software must display
  24. #    the following acknowledgement:
  25. #      "This product includes software developed by Digital Creations
  26. #      for use in the Z Object Publishing Environment
  27. #      (http://www.zope.org/)."
  28. #    In the event that the product being advertised includes an
  29. #    intact Zope distribution (with copyright and license included)
  30. #    then this clause is waived.
  31. # 5. Names associated with Zope or Digital Creations must not be used to
  32. #    endorse or promote products derived from this software without
  33. #    prior written permission from Digital Creations.
  34. # 6. Modified redistributions of any form whatsoever must retain
  35. #    the following acknowledgment:
  36. #      "This product includes software developed by Digital Creations
  37. #      for use in the Z Object Publishing Environment
  38. #      (http://www.zope.org/)."
  39. #    Intact (re-)distributions of any official Zope release do not
  40. #    require an external acknowledgement.
  41. # 7. Modifications are encouraged but must be packaged separately as
  42. #    patches to official Zope releases.  Distributions that do not
  43. #    clearly separate the patches from the original work must be clearly
  44. #    labeled as unofficial distributions.  Modifications which do not
  45. #    carry the name Zope may be packaged in any form, as long as they
  46. #    conform to all of the clauses above.
  47. # Disclaimer
  48. #   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
  49. #   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  50. #   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  51. #   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
  52. #   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  53. #   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  54. #   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  55. #   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  56. #   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  57. #   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  58. #   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  59. #   SUCH DAMAGE.
  60. # This software consists of contributions made by Digital Creations and
  61. # many individuals on behalf of Digital Creations.  Specific
  62. # attributions are listed in the accompanying credits file.
  63. ##############################################################################
  64. """Database objects
  65.  
  66. $Id: DB.py,v 1.20.34.3 2000/09/16 03:05:53 jim Exp $"""
  67. __version__='$Revision: 1.20.34.3 $'[11:-2]
  68.  
  69. import cPickle, cStringIO, sys, POSException, UndoLogCompatible
  70. from Connection import Connection
  71. from bpthread import allocate_lock
  72. from Transaction import Transaction
  73. from referencesf import referencesf
  74. from time import time, ctime
  75.  
  76.  
  77. StringType=type('')
  78.  
  79. class DB(UndoLogCompatible.UndoLogCompatible):
  80.     """The Object Database
  81.  
  82.     The Object database coordinates access to and interaction of one
  83.     or more connections, which manage object spaces.  Most of the actual work
  84.     of managing objects is done by the connections.
  85.     """
  86.  
  87.     def __init__(self, storage,
  88.                  pool_size=7,
  89.                  cache_size=400,
  90.                  cache_deactivate_after=60,
  91.                  version_pool_size=3,
  92.                  version_cache_size=100,
  93.                  version_cache_deactivate_after=10,
  94.                  ):
  95.         """Create an object database.
  96.  
  97.         The storage for the object database must be passed in.
  98.         Optional arguments are:
  99.  
  100.         pool_size -- The size of the pool of object spaces.
  101.  
  102.         """
  103.  
  104.         # Allocate locks:
  105.         l=allocate_lock()
  106.         self._a=l.acquire
  107.         self._r=l.release
  108.  
  109.         # Setup connection pools and cache info
  110.         self._pools={},[]
  111.         self._temps=[]
  112.         self._pool_size=pool_size
  113.         self._cache_size=cache_size
  114.         self._cache_deactivate_after=cache_deactivate_after
  115.         self._version_pool_size=version_pool_size
  116.         self._version_cache_size=version_cache_size
  117.         self._version_cache_deactivate_after=version_cache_deactivate_after
  118.  
  119.         self._miv_cache={}
  120.  
  121.         # Setup storage
  122.         self._storage=storage
  123.         storage.registerDB(self, None)
  124.         if not hasattr(storage,'tpc_vote'): storage.tpc_vote=lambda *args: None
  125.         try: storage.load('\0\0\0\0\0\0\0\0','')
  126.         except:
  127.             import PersistentMapping
  128.             file=cStringIO.StringIO()
  129.             p=cPickle.Pickler(file,1)
  130.             p.dump((PersistentMapping.PersistentMapping,None))
  131.             p.dump({'_container': {}})
  132.             t=Transaction()
  133.             t.description='initial database creation'
  134.             storage.tpc_begin(t)
  135.             storage.store('\0\0\0\0\0\0\0\0', None, file.getvalue(), '', t)
  136.             storage.tpc_vote(t)
  137.             storage.tpc_finish(t)
  138.  
  139.         # Pass through methods:
  140.         for m in ('history',
  141.                   'supportsUndo', 'supportsVersions', 'undoLog',
  142.                   'versionEmpty', 'versions'):
  143.             setattr(self, m, getattr(storage, m))
  144.  
  145.         if hasattr(storage, 'undoInfo'):
  146.             self.undoInfo=storage.undoInfo
  147.  
  148.     def _cacheMean(self, attr):
  149.         m=[0,0]
  150.         def f(con, m=m):
  151.             t=getattr(con._cache,attr)
  152.             m[0]=m[0]+t
  153.             m[1]=m[1]+1
  154.  
  155.         self._connectionMap(f)
  156.         if m[1]: m=m[0]/m[1]
  157.         else: m=None
  158.         return m
  159.  
  160.     def _classFactory(self, connection, location, name,
  161.                       _silly=('__doc__',), _globals={}):
  162.         return getattr(__import__(location, _globals, _globals, _silly),
  163.                        name)
  164.             
  165.     def _closeConnection(self, connection):
  166.         """Return a connection to the pool"""
  167.         self._a()
  168.         try:
  169.             version=connection._version
  170.             pools,pooll=self._pools
  171.             pool, allocated, pool_lock = pools[version]
  172.             pool.append(connection)
  173.             if len(pool)==1:
  174.                 # Pool now usable again, unlock it.
  175.                 pool_lock.release()
  176.         finally: self._r()
  177.         
  178.     def _connectionMap(self, f):
  179.         self._a()
  180.         try:
  181.             pools,pooll=self._pools
  182.             for pool, allocated in pooll:
  183.                 for cc in allocated: f(cc)
  184.  
  185.             temps=self._temps
  186.             if temps:
  187.                 t=[]
  188.                 rc=sys.getrefcount
  189.                 for cc in temps:
  190.                     if rc(cc) > 3: f(cc)
  191.                 self._temps=t
  192.         finally: self._r()
  193.  
  194.     def abortVersion(self, version):
  195.         AbortVersion(self, version)
  196.  
  197.     def cacheDetail(self):
  198.         """Return information on objects in the various caches
  199.  
  200.         Organized by class."""
  201.  
  202.         detail={}
  203.         def f(con,detail=detail,have_detail=detail.has_key):
  204.             for oid, ob in con._cache.items():
  205.                 c="%s.%s" % (ob.__class__.__module__, ob.__class__.__name__)
  206.                 if have_detail(c): detail[c]=detail[c]+1
  207.                 else: detail[c]=1
  208.         
  209.         self._connectionMap(f)
  210.         detail=detail.items()
  211.         detail.sort()
  212.         return detail
  213.  
  214.     def cacheExtremeDetail(self):
  215.         detail=[]
  216.         def f(con, detail=detail, rc=sys.getrefcount):
  217.             for oid, ob in con._cache.items():
  218.                 id=oid
  219.                 if hasattr(ob,'__dict__'):
  220.                     d=ob.__dict__
  221.                     if d.has_key('id'):
  222.                         id="%s (%s)" % (oid, d['id'])
  223.                     elif d.has_key('__name__'):
  224.                         id="%s (%s)" % (oid, d['__name__'])
  225.     
  226.                 detail.append({
  227.                     'oid': id,
  228.                     'klass': "%s.%s" % (ob.__class__.__module__,
  229.                                         ob.__class__.__name__),
  230.                     'rc': rc(ob)-4,
  231.                     'references': con.references(oid),
  232.                     })
  233.  
  234.         self._connectionMap(f)
  235.         return detail
  236.  
  237.     def cacheFullSweep(self, value):
  238.         self._connectionMap(lambda c, v=value: c._cache.full_sweep(v))
  239.  
  240.     def cacheLastGCTime(self):
  241.         m=[0]
  242.         def f(con, m=m):
  243.             t=con._cache.cache_last_gc_time
  244.             if t > m[0]: m[0]=t
  245.  
  246.         self._connectionMap(f)
  247.         return m[0]
  248.  
  249.     def cacheMinimize(self, value):
  250.         self._connectionMap(lambda c, v=value: c._cache.minimize(v))
  251.  
  252.     def cacheMeanAge(self): return self._cacheMean('cache_mean_age')
  253.     def cacheMeanDeac(self): return self._cacheMean('cache_mean_deac')
  254.     def cacheMeanDeal(self): return self._cacheMean('cache_mean_deal')
  255.  
  256.     def cacheSize(self):
  257.         m=[0]
  258.         def f(con, m=m):
  259.             m[0]=m[0]+len(con._cache)
  260.  
  261.         self._connectionMap(f)
  262.         return m[0]
  263.  
  264.     def close(self): self._storage.close()
  265.  
  266.     def commitVersion(self, source, destination=''):
  267.         CommitVersion(self, source, destination)
  268.  
  269.     def exportFile(self, oid, file=None):
  270.         raise 'Not yet implemented'
  271.                            
  272.     def getCacheDeactivateAfter(self): return self._cache_deactivate_after
  273.     def getCacheSize(self): return self._cache_size
  274.  
  275.     def getName(self): return self._storage.getName()
  276.  
  277.     def getPoolSize(self): return self._pool_size
  278.  
  279.     def getSize(self): return self._storage.getSize()
  280.  
  281.     def getVersionCacheDeactivateAfter(self):
  282.         return self._version_cache_deactivate_after
  283.     def getVersionCacheSize(self): return self._version_cache_size
  284.  
  285.     def getVersionPoolSize(self): return self._version_pool_size
  286.  
  287.     def importFile(self, file):
  288.         raise 'Not yet implemented'
  289.  
  290.     def invalidate(self, oid, connection=None, version='',
  291.                    rc=sys.getrefcount):
  292.         """Invalidate references to a given oid.
  293.  
  294.         This is used to indicate that one of the connections has committed a
  295.         change to the object.  The connection commiting the change should be
  296.         passed in to prevent useless (but harmless) messages to the
  297.         connection.
  298.         """
  299.         if connection is not None: version=connection._version
  300.         self._a()
  301.         try:
  302.  
  303.             # Update modified in version cache
  304.             h=hash(oid)%131
  305.             cache=self._miv_cache
  306.             o=cache.get(h, None)
  307.             if o and o[0]==oid: del cache[h]
  308.  
  309.             # Notify connections
  310.             pools,pooll=self._pools
  311.             for pool, allocated in pooll:
  312.                 for cc in allocated:
  313.                     if (cc is not connection and
  314.                         (not version or cc._version==version)):
  315.                         if rc(cc) <= 3:
  316.                             cc.close()
  317.                         cc.invalidate(oid)
  318.  
  319.             temps=self._temps
  320.             if temps:
  321.                 t=[]
  322.                 for cc in temps:
  323.                     if rc(cc) > 3:
  324.                         if (cc is not connection and
  325.                             (not version or cc._version==version)):
  326.                             cc.invalidate(oid)
  327.                         t.append(cc)
  328.                     else: cc.close()
  329.                 self._temps=t
  330.         finally: self._r()
  331.  
  332.     def invalidateMany(self, oids=None, version=''):
  333.         if oids is None: self.invalidate(None, version=version)
  334.         else:
  335.             for oid in oids: self.invalidate(oid, version=version)
  336.  
  337.     def modifiedInVersion(self, oid):
  338.         h=hash(oid)%131
  339.         cache=self._miv_cache
  340.         o=cache.get(h, None)
  341.         if o and o[0]==oid:
  342.             return o[1]
  343.         v=self._storage.modifiedInVersion(oid)
  344.         cache[h]=oid, v
  345.         return v
  346.  
  347.     def objectCount(self): return len(self._storage)
  348.         
  349.     def open(self, version='', transaction=None, temporary=0, force=None,
  350.              waitflag=1):
  351.         """Return a object space (AKA connection) to work in
  352.  
  353.         The optional version argument can be used to specify that a
  354.         version connection is desired.
  355.  
  356.         The optional transaction argument can be provided to cause the
  357.         connection to be automatically closed when a transaction is
  358.         terminated.  In addition, connections per transaction are
  359.         reused, if possible.
  360.  
  361.         Note that the connection pool is managed as a stack, to increate the
  362.         likelihood that the connection's stack will include useful objects.
  363.         """
  364.         if type(version) is not StringType:
  365.             raise POSException.Unimplemented, 'temporary versions'
  366.         
  367.         self._a()
  368.         try:
  369.  
  370.             if transaction is not None:
  371.                 connections=transaction._connections
  372.                 if connections:
  373.                     if connections.has_key(version) and not temporary:
  374.                         return connections[version]
  375.                 else:
  376.                     transaction._connections=connections={}
  377.                 transaction=transaction._connections
  378.                     
  379.  
  380.             if temporary:
  381.                 # This is a temporary connection.
  382.                 # We won't bother with the pools.  This will be
  383.                 # a one-use connection.
  384.                 c=Connection(
  385.                     version=version,
  386.                     cache_size=self._version_cache_size,
  387.                     cache_deactivate_after=
  388.                     self._version_cache_deactivate_after)
  389.                 c._setDB(self)
  390.                 self._temps.append(c)
  391.                 if transaction is not None: transaction[id(c)]=c
  392.                 return c
  393.  
  394.  
  395.             pools,pooll=self._pools
  396.  
  397.             # pools is a mapping object:
  398.             #
  399.             #   {version -> (pool, allocated, lock)
  400.             #
  401.             # where:
  402.             #
  403.             #   pool is the connection pool for the version,
  404.             #   allocated is a list of all of the allocated
  405.             #     connections, and
  406.             #   lock is a lock that is used to block when a pool is
  407.             #     empty and no more connections can be allocated.
  408.             #
  409.             # pooll is a list of all of the pools and allocated for
  410.             # use in cases where we need to iterate over all
  411.             # connections or all inactive connections.
  412.  
  413.             # Pool locks are tricky.  Basically, the lock needs to be
  414.             # set whenever the pool becomes empty so that threads are
  415.             # forced to wait until the pool gets a connection it it.
  416.             # The lock is acquired when the (empty) pool is
  417.             # created. The The lock is acquired just prior to removing
  418.             # the last connection from the pool and just after adding
  419.             # a connection to an empty pool.
  420.  
  421.             
  422.             if pools.has_key(version):
  423.                 pool, allocated, pool_lock = pools[version]
  424.             else:
  425.                 pool, allocated, pool_lock = pools[version] = (
  426.                     [], [], allocate_lock())
  427.                 pooll.append((pool, allocated))
  428.                 pool_lock.acquire()
  429.  
  430.  
  431.             if not pool:
  432.                 c=None
  433.                 if version:
  434.                     if self._version_pool_size > len(allocated) or force:
  435.                         c=Connection(
  436.                             version=version,
  437.                             cache_size=self._version_cache_size,
  438.                             cache_deactivate_after=
  439.                             self._version_cache_deactivate_after)
  440.                         allocated.append(c)
  441.                         pool.append(c)
  442.                 elif self._pool_size > len(allocated) or force:
  443.                     c=Connection(
  444.                         version=version,
  445.                         cache_size=self._cache_size,
  446.                         cache_deactivate_after=
  447.                         self._cache_deactivate_after)
  448.                     allocated.append(c)
  449.                     pool.append(c)
  450.                     
  451.                 if c is None:
  452.                     if waitflag:
  453.                         self._r()
  454.                         pool_lock.acquire()
  455.                         self._a()
  456.                         if len(pool) > 1:
  457.                             # Note that the pool size will normally be 1 here,
  458.                             # but it could be higher due to a race condition.
  459.                             pool_lock.release()
  460.                     else: return
  461.  
  462.             elif len(pool)==1:
  463.                 # Taking last one, lock the pool
  464.                 # Note that another thread might grab the lock
  465.                 # before us, so we might actually block, however,
  466.                 # when we get the lock back, there *will* be a
  467.                 # connection in the pool.
  468.                 self._r()
  469.                 pool_lock.acquire()
  470.                 self._a()
  471.                 if len(pool) > 1:
  472.                     # Note that the pool size will normally be 1 here,
  473.                     # but it could be higher due to a race condition.
  474.                     pool_lock.release()
  475.  
  476.             c=pool[-1]
  477.             del pool[-1]
  478.             c._setDB(self)
  479.             for pool, allocated in pooll:
  480.                 for cc in pool:
  481.                     cc._incrgc()
  482.  
  483.             if transaction is not None: transaction[version]=c
  484.             return c
  485.  
  486.         finally: self._r()
  487.  
  488.     def connectionDebugInfo(self):
  489.         r=[]
  490.         pools,pooll=self._pools
  491.         t=time()
  492.         for version, (pool, allocated, lock) in pools.items():
  493.             for c in allocated:
  494.                 o=c._opened
  495.                 d=c._debug_info
  496.                 if d:
  497.                     if len(d)==1: d=d[0]
  498.                 else: d=''
  499.                 d="%s (%s)" % (d, len(c._cache))
  500.                 
  501.                 r.append({
  502.                     'opened': o and ("%s (%.2fs)" % (ctime(o), t-o)),
  503.                     'info': d,
  504.                     'version': version,
  505.                     })
  506.         return r
  507.         
  508.     def pack(self, t):
  509.         self._storage.pack(t,referencesf)
  510.                            
  511.     def setCacheDeactivateAfter(self, v):
  512.         self._cache_deactivate_after=v
  513.         for c in self._pools[0][''][1]:
  514.             c._cache.cache_age=v
  515.  
  516.     def setCacheSize(self, v):
  517.         self._cache_size=v
  518.         for c in self._pools[0][''][1]:
  519.             c._cache.cache_size=v
  520.  
  521.     def setClassFactory(self, factory):
  522.         self._classFactory=factory
  523.  
  524.     def setPoolSize(self, v): self._pool_size=v
  525.     
  526.     def setVersionCacheDeactivateAfter(self, v):
  527.         self._version_cache_deactivate_after=v
  528.         for ver in self._pools[0].keys():
  529.             if ver:
  530.                 for c in self._pools[0][ver][1]:
  531.                     c._cache.cache_age=v
  532.  
  533.     def setVersionCacheSize(self, v):
  534.         self._version_cache_size=v
  535.         for ver in self._pools[0].keys():
  536.             if ver:
  537.                 for c in self._pools[0][ver][1]:
  538.                     c._cache.cache_size=v
  539.         
  540.     def setVersionPoolSize(self, v): self._version_pool_size=v
  541.  
  542.     def cacheStatistics(self): return () # :(
  543.  
  544.     def undo(self, id):
  545.         for oid in self._storage.undo(id):
  546.             self.invalidate(oid)
  547.  
  548.     def versionEmpty(self, version):
  549.         return self._storage.versionEmpty(version)
  550.  
  551. class CommitVersion:
  552.     """An object that will see to version commit
  553.  
  554.     in cooperation with a transaction manager.
  555.     """
  556.     def __init__(self, db, version, dest=''):
  557.         self._db=db
  558.         s=db._storage
  559.         self._version=version
  560.         self._dest=dest
  561.         self.tpc_abort=s.tpc_abort
  562.         self.tpc_begin=s.tpc_begin
  563.         self.tpc_vote=s.tpc_vote
  564.         self.tpc_finish=s.tpc_finish
  565.         get_transaction().register(self)
  566.  
  567.     def abort(self, reallyme, t): pass
  568.  
  569.     def commit(self, reallyme, t):
  570.         db=self._db
  571.         dest=self._dest
  572.         oids=db._storage.commitVersion(self._version, dest, t)
  573.         for oid in oids: db.invalidate(oid, version=dest)
  574.         if dest:
  575.             # the code above just invalidated the dest version.
  576.             # now we need to invalidate the source!
  577.             for oid in oids: db.invalidate(oid, version=self._version)
  578.     
  579. class AbortVersion(CommitVersion):
  580.     """An object that will see to version abortion
  581.  
  582.     in cooperation with a transaction manager.
  583.     """
  584.  
  585.     def commit(self, reallyme, t):
  586.         db=self._db
  587.         version=self._version
  588.         for oid in db._storage.abortVersion(version, t):
  589.             db.invalidate(oid, version=version)
  590.